您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useState } from 'react'
  4. import {
  5. RiArrowDownDoubleLine,
  6. RiEqualizer2Line,
  7. } from '@remixicon/react'
  8. import { useTranslation } from 'react-i18next'
  9. import { usePathname } from 'next/navigation'
  10. import { useBoolean } from 'ahooks'
  11. import type { AliyunConfig, ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, WeaveConfig } from './type'
  12. import { TracingProvider } from './type'
  13. import TracingIcon from './tracing-icon'
  14. import ConfigButton from './config-button'
  15. import cn from '@/utils/classnames'
  16. import { AliyunIcon, ArizeIcon, LangfuseIcon, LangsmithIcon, OpikIcon, PhoenixIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing'
  17. import Indicator from '@/app/components/header/indicator'
  18. import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps'
  19. import type { TracingStatus } from '@/models/app'
  20. import Toast from '@/app/components/base/toast'
  21. import { useAppContext } from '@/context/app-context'
  22. import Loading from '@/app/components/base/loading'
  23. import Divider from '@/app/components/base/divider'
  24. const I18N_PREFIX = 'app.tracing'
  25. const Panel: FC = () => {
  26. const { t } = useTranslation()
  27. const pathname = usePathname()
  28. const matched = /\/app\/([^/]+)/.exec(pathname)
  29. const appId = (matched?.length && matched[1]) ? matched[1] : ''
  30. const { isCurrentWorkspaceEditor } = useAppContext()
  31. const readOnly = !isCurrentWorkspaceEditor
  32. const [isLoaded, {
  33. setTrue: setLoaded,
  34. }] = useBoolean(false)
  35. const [tracingStatus, setTracingStatus] = useState<TracingStatus | null>(null)
  36. const enabled = tracingStatus?.enabled || false
  37. const handleTracingStatusChange = async (tracingStatus: TracingStatus, noToast?: boolean) => {
  38. await updateTracingStatus({ appId, body: tracingStatus })
  39. setTracingStatus(tracingStatus)
  40. if (!noToast) {
  41. Toast.notify({
  42. type: 'success',
  43. message: t('common.api.success'),
  44. })
  45. }
  46. }
  47. const handleTracingEnabledChange = (enabled: boolean) => {
  48. handleTracingStatusChange({
  49. tracing_provider: tracingStatus?.tracing_provider || null,
  50. enabled,
  51. })
  52. }
  53. const handleChooseProvider = (provider: TracingProvider) => {
  54. handleTracingStatusChange({
  55. tracing_provider: provider,
  56. enabled: true,
  57. })
  58. }
  59. const inUseTracingProvider: TracingProvider | null = tracingStatus?.tracing_provider || null
  60. const providerIconMap: Record<TracingProvider, React.FC<{ className?: string }>> = {
  61. [TracingProvider.arize]: ArizeIcon,
  62. [TracingProvider.phoenix]: PhoenixIcon,
  63. [TracingProvider.langSmith]: LangsmithIcon,
  64. [TracingProvider.langfuse]: LangfuseIcon,
  65. [TracingProvider.opik]: OpikIcon,
  66. [TracingProvider.weave]: WeaveIcon,
  67. [TracingProvider.aliyun]: AliyunIcon,
  68. }
  69. const InUseProviderIcon = inUseTracingProvider ? providerIconMap[inUseTracingProvider] : undefined
  70. const [arizeConfig, setArizeConfig] = useState<ArizeConfig | null>(null)
  71. const [phoenixConfig, setPhoenixConfig] = useState<PhoenixConfig | null>(null)
  72. const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
  73. const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
  74. const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null)
  75. const [weaveConfig, setWeaveConfig] = useState<WeaveConfig | null>(null)
  76. const [aliyunConfig, setAliyunConfig] = useState<AliyunConfig | null>(null)
  77. const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig)
  78. const fetchTracingConfig = async () => {
  79. const getArizeConfig = async () => {
  80. const { tracing_config: arizeConfig, has_not_configured: arizeHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.arize })
  81. if (!arizeHasNotConfig)
  82. setArizeConfig(arizeConfig as ArizeConfig)
  83. }
  84. const getPhoenixConfig = async () => {
  85. const { tracing_config: phoenixConfig, has_not_configured: phoenixHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.phoenix })
  86. if (!phoenixHasNotConfig)
  87. setPhoenixConfig(phoenixConfig as PhoenixConfig)
  88. }
  89. const getLangSmithConfig = async () => {
  90. const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith })
  91. if (!langSmithHasNotConfig)
  92. setLangSmithConfig(langSmithConfig as LangSmithConfig)
  93. }
  94. const getLangFuseConfig = async () => {
  95. const { tracing_config: langFuseConfig, has_not_configured: langFuseHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langfuse })
  96. if (!langFuseHasNotConfig)
  97. setLangFuseConfig(langFuseConfig as LangFuseConfig)
  98. }
  99. const getOpikConfig = async () => {
  100. const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik })
  101. if (!OpikHasNotConfig)
  102. setOpikConfig(opikConfig as OpikConfig)
  103. }
  104. const getWeaveConfig = async () => {
  105. const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave })
  106. if (!weaveHasNotConfig)
  107. setWeaveConfig(weaveConfig as WeaveConfig)
  108. }
  109. const getAliyunConfig = async () => {
  110. const { tracing_config: aliyunConfig, has_not_configured: aliyunHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.aliyun })
  111. if (!aliyunHasNotConfig)
  112. setAliyunConfig(aliyunConfig as AliyunConfig)
  113. }
  114. Promise.all([
  115. getArizeConfig(),
  116. getPhoenixConfig(),
  117. getLangSmithConfig(),
  118. getLangFuseConfig(),
  119. getOpikConfig(),
  120. getWeaveConfig(),
  121. getAliyunConfig(),
  122. ])
  123. }
  124. const handleTracingConfigUpdated = async (provider: TracingProvider) => {
  125. // call api to hide secret key value
  126. const { tracing_config } = await doFetchTracingConfig({ appId, provider })
  127. if (provider === TracingProvider.arize)
  128. setArizeConfig(tracing_config as ArizeConfig)
  129. else if (provider === TracingProvider.phoenix)
  130. setPhoenixConfig(tracing_config as PhoenixConfig)
  131. else if (provider === TracingProvider.langSmith)
  132. setLangSmithConfig(tracing_config as LangSmithConfig)
  133. else if (provider === TracingProvider.langfuse)
  134. setLangFuseConfig(tracing_config as LangFuseConfig)
  135. else if (provider === TracingProvider.opik)
  136. setOpikConfig(tracing_config as OpikConfig)
  137. else if (provider === TracingProvider.weave)
  138. setWeaveConfig(tracing_config as WeaveConfig)
  139. else if (provider === TracingProvider.aliyun)
  140. setAliyunConfig(tracing_config as AliyunConfig)
  141. }
  142. const handleTracingConfigRemoved = (provider: TracingProvider) => {
  143. if (provider === TracingProvider.arize)
  144. setArizeConfig(null)
  145. else if (provider === TracingProvider.phoenix)
  146. setPhoenixConfig(null)
  147. else if (provider === TracingProvider.langSmith)
  148. setLangSmithConfig(null)
  149. else if (provider === TracingProvider.langfuse)
  150. setLangFuseConfig(null)
  151. else if (provider === TracingProvider.opik)
  152. setOpikConfig(null)
  153. else if (provider === TracingProvider.weave)
  154. setWeaveConfig(null)
  155. else if (provider === TracingProvider.aliyun)
  156. setAliyunConfig(null)
  157. if (provider === inUseTracingProvider) {
  158. handleTracingStatusChange({
  159. enabled: false,
  160. tracing_provider: null,
  161. }, true)
  162. }
  163. }
  164. useEffect(() => {
  165. (async () => {
  166. const tracingStatus = await fetchTracingStatus({ appId })
  167. setTracingStatus(tracingStatus)
  168. await fetchTracingConfig()
  169. setLoaded()
  170. })()
  171. }, [])
  172. if (!isLoaded) {
  173. return (
  174. <div className='mb-3 flex items-center justify-between'>
  175. <div className='w-[200px]'>
  176. <Loading />
  177. </div>
  178. </div>
  179. )
  180. }
  181. return (
  182. <div className={cn('flex items-center justify-between')}>
  183. {!inUseTracingProvider && (
  184. <ConfigButton
  185. appId={appId}
  186. readOnly={readOnly}
  187. hasConfigured={false}
  188. enabled={enabled}
  189. onStatusChange={handleTracingEnabledChange}
  190. chosenProvider={inUseTracingProvider}
  191. onChooseProvider={handleChooseProvider}
  192. arizeConfig={arizeConfig}
  193. phoenixConfig={phoenixConfig}
  194. langSmithConfig={langSmithConfig}
  195. langFuseConfig={langFuseConfig}
  196. opikConfig={opikConfig}
  197. weaveConfig={weaveConfig}
  198. aliyunConfig={aliyunConfig}
  199. onConfigUpdated={handleTracingConfigUpdated}
  200. onConfigRemoved={handleTracingConfigRemoved}
  201. >
  202. <div
  203. className={cn(
  204. 'flex cursor-pointer select-none items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
  205. )}
  206. >
  207. <TracingIcon size='md' />
  208. <div className='system-sm-semibold mx-2 text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
  209. <div className='rounded-md p-1'>
  210. <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
  211. </div>
  212. <Divider type='vertical' className='h-3.5' />
  213. <div className='rounded-md p-1'>
  214. <RiArrowDownDoubleLine className='h-4 w-4 text-text-tertiary' />
  215. </div>
  216. </div>
  217. </ConfigButton>
  218. )}
  219. {hasConfiguredTracing && (
  220. <ConfigButton
  221. appId={appId}
  222. readOnly={readOnly}
  223. hasConfigured
  224. enabled={enabled}
  225. onStatusChange={handleTracingEnabledChange}
  226. chosenProvider={inUseTracingProvider}
  227. onChooseProvider={handleChooseProvider}
  228. arizeConfig={arizeConfig}
  229. phoenixConfig={phoenixConfig}
  230. langSmithConfig={langSmithConfig}
  231. langFuseConfig={langFuseConfig}
  232. opikConfig={opikConfig}
  233. weaveConfig={weaveConfig}
  234. aliyunConfig={aliyunConfig}
  235. onConfigUpdated={handleTracingConfigUpdated}
  236. onConfigRemoved={handleTracingConfigRemoved}
  237. >
  238. <div
  239. className={cn(
  240. 'flex cursor-pointer select-none items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
  241. )}
  242. >
  243. <div className='ml-4 mr-1 flex items-center'>
  244. <Indicator color={enabled ? 'green' : 'gray'} />
  245. <div className='system-xs-semibold-uppercase ml-1.5 text-text-tertiary'>
  246. {t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
  247. </div>
  248. </div>
  249. {InUseProviderIcon && <InUseProviderIcon className='ml-1 h-4' />}
  250. <div className='ml-2 rounded-md p-1'>
  251. <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
  252. </div>
  253. <Divider type='vertical' className='h-3.5' />
  254. </div>
  255. </ConfigButton>
  256. )}
  257. </div>
  258. )
  259. }
  260. export default React.memo(Panel)